﻿using System;
using System.Collections.Generic;
using StockholmsStad.Data.Interfaces;
using StockholmsStad.Runtime.Serialization.DataContract;
using StockholmsStad.Runtime.Serialization.DataContract.Enums;
using StockholmsStad.Business.Properties;
using StockholmsStad.Utilities.Enums;
using StockholmsStad.Utilities.Exceptions;

namespace StockholmsStad.Business
{
    /// <summary>
    /// Hanterar affärslogik för ärenden (vigslar)
    /// </summary>
    public class TicketBusiness : BusinessBase
    {
        private const bool useTransaction = true;

        /// <summary>
        /// Skapar businessobjekt som inte kräver autentiserad användare
        /// </summary>
        public TicketBusiness()
            : this(new User { Role = UserRole.ReadOnly })
        {
        }

        /// <summary>
        /// Skapar businessobjekt
        /// </summary>
        /// <param name="user"></param>
        public TicketBusiness(User user) : base(user)
        {
        }

        /// <summary>
        /// Skapar ett nytt ärende för aktuellt par och dess önskemål om ceremoni. Om inget 
        /// ceremonispråk eller certifikatspråk är definierat, använd standardspråken. 
        /// </summary>
        /// <param name="couple">Information om paret</param>
        /// <param name="ceremony">Information om ceremonin</param>
        /// <returns>Ärendenumret</returns>
        public int CreateTicket(Couple couple, Ceremony ceremony)
        {
            //if (CurrentUser.Role == UserRole.ReadOnly)
            //    throw new StockholmStadSystemException(SystemErrorCode.AccessDenied);

            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();
            IUserAgent userAgent = AgentFactory.GetUserAgent();

            if (ceremony == null)
            {
                using (IDataContext context = GetDataContext(false))
                {
                    var languageAgent = AgentFactory.GetLanguageAgent();
                    ceremony = new Ceremony
                                   {
                                       CeremonyLanguage = languageAgent.GetDefaultLanguage(context),
                                       CertificateLanguage = languageAgent.GetDefaultLanguage(context)
                                   };
                }
            }

            int ticketId = 0;

            if (CurrentUser.Role == UserRole.ReadOnly)
            {
                int userId;
                string password;
                string username;

                //IDataContext testContext = GetDataContext(true);
                //password = userAgent.CreatePassword(testContext);

                using (IDataContext publicUserContext = GetDataContext(false))
                {
                    password = userAgent.CreatePassword(publicUserContext);
                    username = couple.PersonDetails.Email;
                    userId = userAgent.CreateUser(publicUserContext, username, password, (int) UserRole.Customer);
                    CurrentUser.Role = UserRole.Customer;
                }

                Exception receivedException = null;
                try
                {
                    using (IDataContext customerUserContext = GetDataContext(useTransaction))
                    {
                        int id = ticketAgent.CreateTicket(customerUserContext, couple, ceremony, userId);
                        ticketAgent.SendRegistrationMail(customerUserContext, couple.PersonDetails, username, password);
                        ticketAgent.InsertHistory(customerUserContext, id, Resources.NewRegistration, username, true);
                        ticketId = id;
                        customerUserContext.TransactionComplete();
                    }
                }
                catch (Exception ex)
                {
                    receivedException = ex;
                    // Something went wrong, we have to clean up the user.
                }
                finally
                {
                    if (receivedException != null)
                    {
                        using (IDataContext customerUserContext = GetDataContext(useTransaction))
                        {
                            userAgent.DeleteUser(customerUserContext, userId);
                            customerUserContext.TransactionComplete();
                        }
                        throw receivedException;
                    }
                }
            }

            if (CurrentUser.Role == UserRole.HandlingOfficer || CurrentUser.Role == UserRole.SysAdmin || CurrentUser.Role == UserRole.SuperUser)
            {
                try
                {
                    using (IDataContext context = GetDataContext(useTransaction))
                    {
                        // If user credentials are provided, create user and send registration mail
                        var userId = CreateUser(context, couple);

                        ticketId = userId.HasValue ?
                            ticketAgent.CreateTicket(context, couple, ceremony, userId.Value) :
                            ticketAgent.CreateTicket(context, couple, ceremony);

                        ticketAgent.InsertHistory(context, ticketId, Resources.NewRegistration, CurrentUser.Username, true);
                        context.TransactionComplete();
                    }
                }
                catch(StockholmStadSystemException)
                {
                    throw;
                }
                catch(Exception e)
                {
                    throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
                }
            }

            return ticketId;
        }

        /// <summary>
        /// Hämtar ärendeid-numret utifrån parets nyckel. 
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public int GetTicketId(string username)
        {
            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();
            using (IDataContext context = GetDataContext(false))
            {
                return ticketAgent.GetTicketId(context, username);
            }
        }

        /// <summary>
        /// Hämtar ärende utifrån ticketId
        /// </summary>
        /// <param name="ticketId"></param>
        /// <returns></returns>
        public Ticket GetTicket(int ticketId)
        {
            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();
            using (IDataContext context = GetDataContext(false))
            {
                return ticketAgent.GetTicket(context, ticketId);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ticketId"></param>
        /// <returns></returns>
        public Couple GetCouple(int ticketId)
        {
            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();
            using (IDataContext context = GetDataContext(false))
            {
                return ticketAgent.GetCouple(context, ticketId);
            }
        }

        /// <summary>
        /// Hämtar alla aktuella ärenden
        /// </summary>
        /// <returns></returns>
        public IEnumerable<CouplesSummaryItem> GetTickets(string partOfName, string partOfBirthday, TicketStatusType ticketStatus)
        {
            if (CurrentUser.Role == UserRole.Customer)
                throw new StockholmStadSystemException(SystemErrorCode.AccessDenied);

            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();
            using (IDataContext context = GetDataContext(false))
            {
                return ticketAgent.GetTickets(context, partOfName, partOfBirthday, ticketStatus);
            }
        }

        /// <summary>
        /// Uppdaterar ett par
        /// </summary>
        /// <param name="ticketId"></param>
        /// <param name="couple"></param>
        public void UpdateCouple(int ticketId, Couple couple)
        {
            if (CurrentUser.Role == UserRole.ReadOnly || CurrentUser.Role == UserRole.Customer)
                throw new StockholmStadSystemException(SystemErrorCode.AccessDenied);

            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(useTransaction))
            {
                //Check if user is new/changed/removed
                ChangeUser(context, ticketId, couple);

                ticketAgent.UpdateCouple(context, ticketId, couple);
                ticketAgent.InsertHistory(context, ticketId, Resources.CeremonyUpdated, CurrentUser.Username, false);
                
                
                context.TransactionComplete();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ticketId"></param>
        /// <param name="ticketStatusType"></param>
        public void UpdateTicketStatus(int ticketId, TicketStatusType ticketStatusType)
        {
            if (CurrentUser.Role == UserRole.ReadOnly || CurrentUser.Role == UserRole.Customer)
                throw new StockholmStadSystemException(SystemErrorCode.AccessDenied);

            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(useTransaction))
            {
                ticketAgent.UpdateTicketStatus(context, ticketId, ticketStatusType);
                context.TransactionComplete();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ceremonies"></param>
        public void UpdateTicketsWithStatus(List<CeremonySummaryDetails> ceremonies)
        {
            if (CurrentUser.Role == UserRole.ReadOnly || CurrentUser.Role == UserRole.Customer)
                throw new StockholmStadSystemException(SystemErrorCode.AccessDenied);

            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(useTransaction))
            {
                foreach (var ceremony in ceremonies)
                {
                    ticketAgent.UpdateTicketStatus(context, ceremony.TicketId, ceremony.TicketStatusType);
                }
                context.TransactionComplete();
            }
        }

        /// <summary>
        /// Uppdaterar ett ärende
        /// </summary>
        /// <param name="couple"></param>
        /// <param name="ceremony"></param>
        /// <param name="ticketId"></param>
        public void UpdateTicket(Couple couple, Ceremony ceremony, int ticketId)
        {
            //if (CurrentUser.Role == UserRole.ReadOnly || CurrentUser.Role == UserRole.Customer)
            if (CurrentUser.Role == UserRole.ReadOnly)
                throw new StockholmStadSystemException(SystemErrorCode.AccessDenied);

            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();
            string username = (couple.ContactPerson == null) ? couple.Person1.Email : couple.ContactPerson.Email;

            using (IDataContext context = GetDataContext(useTransaction))
            {
                // If user credentials are provided, create user and send registration mail
                ChangeUser(context, ticketId, couple);

                ticketAgent.UpdateTicket(context, ticketId, couple, ceremony, username);
                
                context.TransactionComplete();
            }
        }

        /// <summary>
        /// Hämtar historik för den inloggade användaren
        /// </summary>
        /// <returns>En lista med historik (<see cref="History"/>). </returns>
        public IEnumerable<History> GetHistoryForCurrentUser()
        {
            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(false))
            {
                var ticketId = ticketAgent.GetTicketId(context, CurrentUser.Username);
                return ticketAgent.GetHistoryItems(context, ticketId, true);
            }
        }

        /// <summary>
        /// Hämtar övrig information för ett ärende. 
        /// </summary>
        /// <param name="ticketId">Nyckeln på ärendet som avses</param>
        /// <returns>Den övriga informationen (<see cref="TicketInformation"/>).</returns>
        public TicketInformation GetTicketInformation(int ticketId)
        {
            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(false))
            {
                return ticketAgent.GetTicketInformation(context, ticketId);
            }                        
        }

        /// <summary>
        /// Lägger till övrig information till ett ärende. 
        /// </summary>
        /// <param name="ticketInformation">Den information som ska läggas till. </param>
        /// <returns>Id-numret på den information som lades till. </returns>
        public int AddTicketInformation(TicketInformation ticketInformation)
        {
            if (CurrentUser.Role == UserRole.ReadOnly || CurrentUser.Role == UserRole.Customer)
                throw new StockholmStadSystemException(SystemErrorCode.AccessDenied);

            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(useTransaction))
            {
                var id = ticketAgent.AddTicketInformation(context, ticketInformation);
                context.TransactionComplete();
                return id;
            }                                    
        }

        /// <summary>
        /// Lägger till övrig information till ett ärende. 
        /// </summary>
        /// <param name="ticketInformation">Den information som ska läggas till. </param>
        /// <returns>Id-numret på den information som lades till. </returns>
        public void UpdateTicketInformation(TicketInformation ticketInformation)
        {
            if (CurrentUser.Role == UserRole.ReadOnly || CurrentUser.Role == UserRole.Customer)
                throw new StockholmStadSystemException(SystemErrorCode.AccessDenied);

            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(useTransaction))
            {
                ticketAgent.UpdateTicketInformation(context, ticketInformation);
                context.TransactionComplete();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public List<History> GetNotices()
        {
            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(false))
            {
                return ticketAgent.GetNotices(context);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="historyId"></param>
        public void InactivateNotice(int historyId)
        {
            if (CurrentUser.Role == UserRole.ReadOnly || CurrentUser.Role == UserRole.Customer)
                throw new StockholmStadSystemException(SystemErrorCode.AccessDenied);

            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(useTransaction))
            {
                ticketAgent.InactivateNotice(context, historyId);
                context.TransactionComplete();
            }            
        }

        /// <summary>
        /// Returnerar true om användarnamnet är unikt
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public bool IsUserNameUnique(string username)
        {
            //Om befintlig användare, kolla om username har ändrats eller ej
            if (CurrentUser != null && CurrentUser.Username.Equals(username))
                return true;

            var userAgent = AgentFactory.GetUserAgent();
            using (IDataContext context = GetDataContext(true))
            {
                return userAgent.IsUserNameUnique(context, username);
            }
        }

        /// <summary>
        /// Hämtar all historik för ett ärende
        /// </summary>
        /// <param name="ticketId">Nyckeln på ärendet som avses</param>
        /// <returns>En lista med historik (<see cref="History"/>). </returns>
        public IEnumerable<History> GetHistory(int ticketId)
        {
            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(false))
            {
                return ticketAgent.GetHistoryItems(context, ticketId, false);
            }            
        }

        /// <summary>
        /// Hämtar ceremoniinformation för ett ärende
        /// </summary>
        /// <param name="ticketId">Nyckeln på ärendet som avses</param>
        /// <returns>Ceremoniinformationen. (<see cref="Ceremony"/>). </returns>
        public CeremonyBooking GetCeremony(int ticketId)
        {
            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(false))
            {
                var ceremonyBooking = new CeremonyBooking();

                var ceremony = ticketAgent.GetCeremony(context, ticketId);
                ceremonyBooking.Ceremony = ceremony;

                var ministerAgent = AgentFactory.GetMinisterAgent();
                var minister = ministerAgent.GetMinisterByTicket(context, ticketId);
                ceremonyBooking.Minister = minister;

                return ceremonyBooking;
            }
        }

        /// <summary>
        /// Hämtar icke-lediga tidsblock som en lista med start-slut-datum (för kalender som 
        /// endast tar "utblockade" datum. 
        /// </summary>
        /// <returns>En lista med </returns>
        public List<DateRange> GetNonAvailableDates()
        {
            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(false))
            {
                return ticketAgent.GetNonAvailableDates(context);
            }      
        }
    
        /// <summary>
        /// Hämtar icke fullbokade tidsblock som en lista med start-slut-datum (för kalender som 
        /// endast tar "utblockade" datum. 
        /// </summary>
        /// <returns></returns>
        public List<DateRange> GetNonFullyBookedDates()
        {
            ITicketAgent ticketAgent = AgentFactory.GetTicketAgent();

            using (IDataContext context = GetDataContext(false))
            {
                return ticketAgent.GetNonFullyBookedDates(context);
            }
        }

        private int? CreateUser(IDataContext context, Couple couple)
        {
            int? userId = null;
            string email = string.Empty;
            string fullname = string.Empty;
            if(couple.ContactPerson != null && !string.IsNullOrEmpty(couple.ContactPerson.Email))
            {
                email = couple.ContactPerson.Email;
                fullname = string.Format("{0} {1}", couple.ContactPerson.FirstNames, couple.ContactPerson.LastName);
            }
            else if(couple.Person1 != null && !string.IsNullOrEmpty(couple.Person1.Email))
            {
                email = couple.Person1.Email;
                fullname = string.Format("{0} {1}", couple.Person1.FirstNames, couple.Person1.LastName);
            }

            var userAgent = AgentFactory.GetUserAgent();

            if (!userAgent.IsUserNameUnique(context, email))
                throw new StockholmStadSystemException(SystemErrorCode.UserAllreadyExist);

            if(!string.IsNullOrEmpty(email))
            {
                // Skapa användare
                string password = userAgent.CreatePassword(context);

                userId = userAgent.CreateUser(context, email, password, (int) UserRole.Customer);

                // Send registration mail
                var ticketAgent = AgentFactory.GetTicketAgent();
                ticketAgent.SendRegistrationMail(context, email, password, fullname);
            }

            return userId;
        }

        private int? ChangeUser(IDataContext context, int ticketId, Couple couple)
        {
            int? userId = null;
            string email = string.Empty;
            string fullname = string.Empty;
            string oldEmail = string.Empty;
            if (couple.ContactPerson != null && !string.IsNullOrEmpty(couple.ContactPerson.Email))
            {
                email = couple.ContactPerson.Email;
                fullname = string.Format("{0} {1}", couple.ContactPerson.FirstNames, couple.ContactPerson.LastName);
            }
            else if (couple.Person1 != null && !string.IsNullOrEmpty(couple.Person1.Email))
            {
                email = couple.Person1.Email;
                fullname = string.Format("{0} {1}", couple.Person1.FirstNames, couple.Person1.LastName);
            }

            var ticketAgent = AgentFactory.GetTicketAgent();
            var userAgent = AgentFactory.GetUserAgent();

            var ticket = ticketAgent.GetTicket(context, ticketId);
            if (ticket.Couple.ContactPerson != null)
            {
                oldEmail = ticket.Couple.ContactPerson.Email;
            }
            if (ticket.Couple.Person1 != null && ticket.Couple.Person1.Email != null)
            {
                oldEmail = ticket.Couple.Person1.Email;
            }

            //ta bort gammal användare om det inte finns en ny eller email har ändrats
            if (!string.IsNullOrEmpty(oldEmail) && (string.IsNullOrEmpty(email) || oldEmail != email))
            {
                var user = userAgent.GetUser(context, oldEmail);
                if (user != null)
                {
                    userAgent.ChangeUser(context, ticketId, null);
                    userAgent.DeleteUser(context, user.UserId);
                }
            }

            //Ny eller ändrad användare - skapa med nytt password och skicka mail
            if (!string.IsNullOrEmpty(email) && email != oldEmail)
            {
                //Kolla att det är en unik email adress
                if (!userAgent.IsUserNameUnique(context, email))
                    throw new StockholmStadSystemException(SystemErrorCode.UserAllreadyExist);

                // Skapa användare
                string password = userAgent.CreatePassword(context);

                userId = userAgent.CreateUser(context, email, password, (int)UserRole.Customer);

                // Send registration mail
                ticketAgent.SendRegistrationMail(context, email, password, fullname);
            }

            if(!userId.HasValue && !string.IsNullOrEmpty(oldEmail))
            {
                var user = userAgent.GetUser(context, oldEmail);
                if (user != null)
                {
                    userId = user.UserId;
                }
            }

            userAgent.ChangeUser(context, ticketId, userId);

            return userId;
        }
    }
}